#include "Spark/Net/Channel.h"
#include "Spark/Net/Impl/EpollPoller.h"
#include "Spark/Utils/Logger.h"

#include <unistd.h>
#include <string.h>
#include <assert.h>

static_assert(EPOLLIN == POLLIN, "EPOLLIN != POLLIN");
static_assert(EPOLLPRI == POLLPRI, "EPOLLPRI != POLLPRI");
static_assert(EPOLLOUT == POLLOUT, "EPOLLOUT != POLLOUT");
static_assert(EPOLLRDHUP == POLLRDHUP, "EPOLLRDHUP != POLLRDHUP");
static_assert(EPOLLERR == POLLERR, "EPOLLERR != POLLERR");
static_assert(EPOLLHUP == POLLHUP, "EPOLLHUP != POLLHUP");

static const int kInitEventListSize = 16;

EpollPoller::EpollPoller(EventLoop* loop)
    : Poller(loop), epollFD_(::epoll_create1(EPOLL_CLOEXEC))
{
    if (epollFD_ < 0)
    {
        LOG_SYSERR << "epoll_create1";
    }
}

EpollPoller::~EpollPoller()
{
    ::close(epollFD_);
}

void EpollPoller::poll(int timeout, ChannelList* activeChannels)
{
    int nEvents = ::epoll_wait(epollFD_,
                               &*epollEvents_.begin(),
                               static_cast<int>(epollEvents_.size()),
                               timeout);
    int savedErrno = errno;
    if (nEvents > 0)  // nEvents happened
    {
        dispatchEvents(nEvents, activeChannels);
    }
    else if (nEvents == 0)
    {
        LOG_TRACE << "epoll_wait timeout";
    }
    else
    {
        if (savedErrno != EINTR)
        {
            errno = savedErrno;
            LOG_SYSERR << "EpollPoller::poll";
        }
    }
}

void EpollPoller::update(EpollOpt opt, Channel* ch)
{
    int optval = static_cast<int>(opt);
    epoll_event event{};
    event.events = ch->eventMask();
    event.data.ptr = ch;
    int fd = ch->fd();
    if (::epoll_ctl(epollFD_, optval, fd, &event) < 0)
    {
        if (opt == EpollOpt::epDEL)
        {
            LOG_SYSERR << "EpollPoller::update DEL fd=" << fd;
        }
        else
        {
            LOG_SYSERR << "EpollPoller::update ADD or MOD fd=" << fd;
        }
    }
}

void EpollPoller::updateChannel(Channel* ch)
{
    assert(loop_->isInLoopThread());
    Channel::Status s = ch->status();
    if (s == Channel::Status::kNew || s == Channel::Status::kDeleted)
    {
        ch->setStatus(Channel::Status::kAdded);
        update(EpollOpt::epADD, ch);
    }
    else
    {
        assert(s == Channel::Status::kAdded);
        if (ch->isNonEvent())
        {
            update(EpollOpt::epDEL, ch);
            ch->setStatus(Channel::Status::kDeleted);
        }
        else
        {
            update(EpollOpt::epMOD, ch);
        }
    }
}

void EpollPoller::removeChannel(Channel* ch)
{
    assert(ch->isNonEvent());
    auto status = ch->status();
    assert(status == Channel::Status::kAdded ||
           status == Channel::Status::kDeleted);
    if (status == Channel::Status::kAdded)
        update(EpollOpt::epDEL, ch);
    ch->setStatus(Channel::Status::kNew);
}

void EpollPoller::dispatchEvents(int nEvents, ChannelList* activeChannels)
{
    assert(nEvents <= kMaxEvents);
    for (int i = 0; i < nEvents; ++i)
    {
        Channel* ch = static_cast<Channel*>(epollEvents_[i].data.ptr);
        ch->setOccurred(epollEvents_[i].events);
        activeChannels->push_back(ch);
    }
}
